﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.XPath;

using WebDavProcessor.Constants;
using WebDavProcessor.Enumerations;
using WebDavProcessor.Interfaces;
using WebDavProcessor.Resources;

namespace WebDavProcessor.Methods
{
    public class WebDavPropFind : WebDavMethod
    {
        private List<string> properties;
        private bool allProp;
        private bool propName;

        public List<string> Properties { get { return properties; } }
        public bool AllProperties { get { return allProp; } }
        public bool PropertyNames { get { return propName; } }

        public WebDavPropFind(IWebDavRequest webDavRequest)
            : base(webDavRequest)
        {
            this.LoadProperties();
        }
        public WebDavPropFind(string requestMethod, Uri requestUri, NameValueCollection requestHeaders, Stream requestInputStream)
            : base(requestMethod, requestUri, requestHeaders, requestInputStream)
        {
            this.LoadProperties();
        }

        private void LoadProperties()
        {
            properties = new List<string>();
            propName = false;
            allProp = false;
            try
            {
                this.InputStream.Position = 0;
                XPathDocument document = new XPathDocument(this.InputStream);
                XPathNavigator navigator = document.CreateNavigator();
                XPathNodeIterator iterator = navigator.Select("/" + WebDavXML.PropFind + "/*");
                if (iterator.Count == 1)
                {
                    iterator.MoveNext();
                    if (iterator.Current.LocalName == WebDavXML.PropName)
                    {
                        propName = true;
                        return;
                    }
                    if (iterator.Current.LocalName == WebDavXML.AllProp)
                    {
                        allProp = true;
                        return;
                    }
                    if (iterator.Current.LocalName == WebDavXML.Prop)
                    {
                        iterator = iterator.Current.Select("*");
                        while (iterator.MoveNext())
                        {
                            properties.Add(iterator.Current.LocalName);
                        }
                    }
                    return;
                }
                if (iterator.Count == 2)
                {
                    iterator.MoveNext();
                    if (iterator.Current.LocalName == WebDavXML.AllProp)
                    {
                        iterator.MoveNext();
                        if (iterator.Current.LocalName == WebDavXML.Include)
                        {
                            iterator = iterator.Current.Select("*");
                            while (iterator.MoveNext())
                            {
                                properties.Add(iterator.Current.LocalName);
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw new WebDavException(WebDavStatusCodes.InternalServerError);
            }
        }

        public override void ProcessRequest(IWebDavResponse webDavResponse, IWebDavResourceProvider resourceProvider)
        {
            if (!propName && !allProp && properties.Count == 0)
            {
                WebDavMethod.SetError(webDavResponse, WebDavStatusCodes.BadRequest);
                return;
            }

            WebDavResource resource;
            if (!(resourceProvider is IWebDavPropertyProvider))
            {
                WebDavMethod.SetError(webDavResponse, WebDavStatusCodes.NotImplemented);
                return;
            }

            try
            {
                WebDavDepth depth;
                switch (this.Headers[WebDavHeaders.Depth])
                {
                    case WebDavHeaderValues.DepthValue0:
                        depth = WebDavDepth.Zero;
                        break;
                    case WebDavHeaderValues.DepthValue1:
                        depth = WebDavDepth.One;
                        break;
                    default:
                        depth = WebDavDepth.Infinity;
                        break;
                }

                if (propName)
                {
                    resource = (resourceProvider as IWebDavPropertyProvider).GetPropertyNames(this.RequestUri.ToString(), depth);
                }
                else if (allProp)
                {
                    if (properties.Count == 0)
                    {
                        resource = (resourceProvider as IWebDavPropertyProvider).GetAllProperties(this.RequestUri.ToString(), depth);
                    }
                    else
                    {
                        resource = (resourceProvider as IWebDavPropertyProvider).GetAllProperties(this.RequestUri.ToString(), properties, depth);
                    }
                }
                else
                {
                    resource = (resourceProvider as IWebDavPropertyProvider).GetProperties(this.RequestUri.ToString(), properties, depth);
                }

                if (resource == null)
                {
                    WebDavMethod.SetError(webDavResponse, WebDavStatusCodes.NotImplemented);
                    return;
                }

                MemoryStream stream = new MemoryStream();
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Encoding = Encoding.UTF8;
                XmlWriter writer = XmlWriter.Create(stream, settings);
                writer.WriteStartDocument();
                writer.WriteStartElement(WebDavXML.DefaultPrefix, WebDavXML.MultiStatus, WebDavXML.DefaultNameSpace);
                if (resource is WebDavResourceCollection)
                {
                    WebDavResourceCollection resourceProperties = (resource as WebDavResourceCollection);
                    if (propName)
                    {
                        foreach (WebDavResource currentResource in resourceProperties.Data)
                        {
                            writer.WriteStartElement(WebDavXML.Response, WebDavXML.DefaultNameSpace);
                            writer.WriteStartElement(WebDavXML.Href, WebDavXML.DefaultNameSpace);
                            writer.WriteString(currentResource.Path);
                            writer.WriteEndElement();
                            writer.WriteStartElement(WebDavXML.PropStat, WebDavXML.DefaultNameSpace);
                            writer.WriteRaw("<" + WebDavXML.DefaultPrefix + ":" + WebDavXML.Prop + " xmlns:" + WebDavXML.UserPrefix + "=\"" + WebDavXML.UserNameSpace + "\">");
                            foreach (WebDavProperty currentProperty in (currentResource as WebDavResourceProperties).Properties)
                            {
                                if (currentProperty.Name == WebDavXML.GetContentLanguage || currentProperty.Name == WebDavXML.GetContentLength ||
                                    currentProperty.Name == WebDavXML.GetContentType || currentProperty.Name == WebDavXML.GetETag ||
                                    currentProperty.Name == WebDavXML.GetLastModified || currentProperty.Name == WebDavXML.CreationDate ||
                                    currentProperty.Name == WebDavXML.DisplayName || currentProperty.Name == WebDavXML.LockDiscovery ||
                                    currentProperty.Name == WebDavXML.ResourceType || currentProperty.Name == WebDavXML.SupportedLock)
                                {
                                    writer.WriteRaw("<" + WebDavXML.DefaultPrefix + ":" + currentProperty.Name + "/>");
                                }
                                else
                                {
                                    writer.WriteRaw("<" + WebDavXML.UserPrefix + ":" + currentProperty.Name + "/>");
                                }
                            }
                            writer.WriteRaw("</" + WebDavXML.DefaultPrefix + ":" + WebDavXML.Prop + ">");
                            writer.WriteStartElement(WebDavXML.Status, WebDavXML.DefaultNameSpace);
                            writer.WriteString(WebDavXML.DefaultHttpVersion + " " + WebDavStatusCodes.OK.ToString() + " " + WebDavStatusDescriptions.OK);
                            writer.WriteEndElement();
                            writer.WriteEndElement();
                            writer.WriteEndElement();
                        }
                    }
                    else
                    {
                        foreach (WebDavResource currentResource in resourceProperties.Data)
                        {
                            writer.WriteStartElement(WebDavXML.Response, WebDavXML.DefaultNameSpace);
                            writer.WriteStartElement(WebDavXML.Href, WebDavXML.DefaultNameSpace);
                            writer.WriteString(currentResource.Path);
                            writer.WriteEndElement();
                            writer.WriteStartElement(WebDavXML.PropStat, WebDavXML.DefaultNameSpace);
                            writer.WriteRaw("<" + WebDavXML.DefaultPrefix + ":" + WebDavXML.Prop + " xmlns:" + WebDavXML.UserPrefix + "=\"" + WebDavXML.UserNameSpace + "\">");
                            foreach (WebDavProperty currentProperty in (currentResource as WebDavResourceProperties).Properties)
                            {
                                if (currentProperty.Name == WebDavXML.GetContentLanguage || currentProperty.Name == WebDavXML.GetContentLength ||
                                    currentProperty.Name == WebDavXML.GetContentType || currentProperty.Name == WebDavXML.GetETag ||
                                    currentProperty.Name == WebDavXML.GetLastModified || currentProperty.Name == WebDavXML.CreationDate ||
                                    currentProperty.Name == WebDavXML.DisplayName || currentProperty.Name == WebDavXML.LockDiscovery ||
                                    currentProperty.Name == WebDavXML.ResourceType || currentProperty.Name == WebDavXML.SupportedLock)
                                {
                                    writer.WriteRaw("<" + WebDavXML.DefaultPrefix + ":" + currentProperty.Name + ">");
                                    writer.WriteString(currentProperty.Value.ToString());
                                    writer.WriteRaw("<" + WebDavXML.DefaultPrefix + ":" + currentProperty.Name + "/>");
                                }
                                else
                                {
                                    writer.WriteRaw("<" + WebDavXML.UserPrefix + ":" + currentProperty.Name + "/>");
                                    writer.WriteString(currentProperty.Value.ToString());
                                    writer.WriteRaw("<" + WebDavXML.UserPrefix + ":" + currentProperty.Name + "/>");
                                }
                            }
                            writer.WriteRaw("</" + WebDavXML.DefaultPrefix + ":" + WebDavXML.Prop + ">");
                            writer.WriteStartElement(WebDavXML.Status, WebDavXML.DefaultNameSpace);
                            writer.WriteString(WebDavXML.DefaultHttpVersion + " " + WebDavStatusCodes.OK.ToString() + " " + WebDavStatusDescriptions.OK);
                            writer.WriteEndElement();
                            writer.WriteEndElement();
                            writer.WriteEndElement();
                        }
                    }
                    writer.WriteEndElement();
                    writer.Flush();
                    writer.Close();

                    stream.Position = 0;

                    webDavResponse.StatusCode = WebDavStatusCodes.MultiStatus;
                    webDavResponse.StatusDescription = WebDavStatusDescriptions.MultiStatus;
                    webDavResponse.Headers[WebDavHeaders.ContentType] = WebDavXML.DefaultContentType + "; " + WebDavXML.DefaultCharset;
                    webDavResponse.Headers[WebDavHeaders.ContentLength] = stream.Length.ToString();
                    webDavResponse.OutputStream = stream;
                    return;
                }
                if (resource is WebDavResourceProperties)
                {
                    WebDavResourceProperties resourceProperties = (resource as WebDavResourceProperties);
                    if (resourceProperties.Properties.Count == 0)
                    {
                        WebDavMethod.SetError(webDavResponse, WebDavStatusCodes.NotImplemented);
                        return;
                    }

                    writer.WriteStartElement(WebDavXML.Response, WebDavXML.DefaultNameSpace);
                    writer.WriteStartElement(WebDavXML.Href, WebDavXML.DefaultNameSpace);
                    writer.WriteString(resourceProperties.Path);
                    writer.WriteEndElement();
                    writer.WriteStartElement(WebDavXML.PropStat, WebDavXML.DefaultNameSpace);
                    writer.WriteRaw("<" + WebDavXML.DefaultPrefix + ":" + WebDavXML.Prop + " xmlns:" + WebDavXML.UserPrefix + "=\"" + WebDavXML.UserNameSpace + "\">");

                    if (propName)
                    {
                        foreach (WebDavProperty currentProperty in resourceProperties.Properties)
                        {
                            if (currentProperty.Name == WebDavXML.GetContentLanguage || currentProperty.Name == WebDavXML.GetContentLength ||
                                currentProperty.Name == WebDavXML.GetContentType || currentProperty.Name == WebDavXML.GetETag ||
                                currentProperty.Name == WebDavXML.GetLastModified || currentProperty.Name == WebDavXML.CreationDate ||
                                currentProperty.Name == WebDavXML.DisplayName || currentProperty.Name == WebDavXML.LockDiscovery ||
                                currentProperty.Name == WebDavXML.ResourceType || currentProperty.Name == WebDavXML.SupportedLock)
                            {
                                writer.WriteRaw("<" + WebDavXML.DefaultPrefix + ":" + currentProperty.Name + "/>");
                            }
                            else
                            {
                                writer.WriteRaw("<" + WebDavXML.UserPrefix + ":" + currentProperty.Name + "/>");
                            }
                        }
                    }
                    else
                    {
                        foreach (WebDavProperty currentProperty in resourceProperties.Properties)
                        {
                            if (currentProperty.Name == WebDavXML.GetContentLanguage || currentProperty.Name == WebDavXML.GetContentLength ||
                                currentProperty.Name == WebDavXML.GetContentType || currentProperty.Name == WebDavXML.GetETag ||
                                currentProperty.Name == WebDavXML.GetLastModified || currentProperty.Name == WebDavXML.CreationDate ||
                                currentProperty.Name == WebDavXML.DisplayName || currentProperty.Name == WebDavXML.LockDiscovery ||
                                currentProperty.Name == WebDavXML.ResourceType || currentProperty.Name == WebDavXML.SupportedLock)
                            {
                                writer.WriteRaw("<" + WebDavXML.DefaultPrefix + ":" + currentProperty.Name + ">");
                                writer.WriteString(currentProperty.Value.ToString());
                                writer.WriteRaw("<" + WebDavXML.DefaultPrefix + ":" + currentProperty.Name + "/>");
                            }
                            else
                            {
                                writer.WriteRaw("<" + WebDavXML.UserPrefix + ":" + currentProperty.Name + "/>");
                                writer.WriteString(currentProperty.Value.ToString());
                                writer.WriteRaw("<" + WebDavXML.UserPrefix + ":" + currentProperty.Name + "/>");
                            }
                        }
                    }

                    writer.WriteRaw("</" + WebDavXML.DefaultPrefix + ":" + WebDavXML.Prop + ">");
                    writer.WriteStartElement(WebDavXML.Status, WebDavXML.DefaultNameSpace);
                    writer.WriteString(WebDavXML.DefaultHttpVersion + " " + WebDavStatusCodes.OK.ToString() + " " + WebDavStatusDescriptions.OK);
                    writer.WriteEndElement();
                    writer.WriteEndElement();
                    writer.WriteEndElement();
                    writer.WriteEndElement();
                    writer.Flush();
                    writer.Close();

                    stream.Position = 0;

                    webDavResponse.StatusCode = WebDavStatusCodes.MultiStatus;
                    webDavResponse.StatusDescription = WebDavStatusDescriptions.MultiStatus;
                    webDavResponse.Headers[WebDavHeaders.ContentType] = WebDavXML.DefaultContentType + "; " + WebDavXML.DefaultCharset;
                    webDavResponse.Headers[WebDavHeaders.ContentLength] = stream.Length.ToString();
                    webDavResponse.OutputStream = stream;
                    return;
                }
                WebDavMethod.SetError(webDavResponse, resource.Status);
            }
            catch (WebDavException e)
            {
                WebDavMethod.SetError(webDavResponse, e.StatusCode);
                return;
            }
            catch (Exception)
            {
                WebDavMethod.SetError(webDavResponse, WebDavStatusCodes.InternalServerError);
                return;
            }
        }
    }
}
